package com.xfcode.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONObject;
import com.xfcode.api.WikiDTO;
import com.xfcode.api.vo.CreateWikiVO;
import com.xfcode.service.EsService;
import com.xfcode.service.SearcherBaseService;
import com.xfcode.utils.KmStringUtils;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.AnalyzeRequest;
import org.elasticsearch.client.indices.AnalyzeResponse;
import org.elasticsearch.client.indices.IndexTemplatesExistRequest;
import org.elasticsearch.client.indices.PutIndexTemplateRequest;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class EsServiceImpl extends SearcherBaseService implements EsService {
    private  String LogKbWikiAliasName = "log_prod_kb_wiki_alias";
    private  String LogKbWikiAlias = "log_prod_kb_wiki";
    @Override
    public String getEsAliasName() {
        return  LogKbWikiAliasName;
    }

    @Override
    public String getEsAlias() {
        return LogKbWikiAlias;
    }

    @Autowired
    private RestHighLevelClient restHighLevelClient;
    public String saveToEs(CreateWikiVO createWikiVO){
        WikiDTO wikiDTO = buildWikiDTO(createWikiVO);
        //入库ES
        try {
            IndexResponse response = getAddIndexResponse(wikiDTO);
            if (!response.status().equals(RestStatus.CREATED)) {
                log.error("入库ES发生错误，返回码:" + response.status().toString() );
            }
            else{
                log.info("记录入库ES成功");
                return response.getId();
            }
        }
        catch (Exception e){
            log.error("入库ES发生错误" ,e );
        }
        return null;
    }


    @Override
    public void updateById(String indexId, CreateWikiVO createWikiVO) {
        if (ObjectUtils.isEmpty(indexId)){
            return;
        }
        WikiDTO wikiDTO = buildWikiDTO(createWikiVO);
        //更新ES记录
        try {
            UpdateResponse updateResponse = getUpdateResponse(indexId, wikiDTO);
            if (!updateResponse.status().equals(RestStatus.OK)) {
                log.error("更新ES发生错误，返回码[" + updateResponse.status().toString() + "]");
            } else {
                log.info("更新ES成功");
            }
        } catch (Exception e){
            log.error("更新ES发生错误" ,e );
        }
    }



    public void deleteById(String indexId){
        try {
            SearchResponse searchResponse = getSearchResponse(indexId);
            if(searchResponse.status() != RestStatus.OK){
                log.info("从ES查询文档索引失败");
            }
            else {
                long c = searchResponse.getHits().getTotalHits().value;
                if(c == 0){
                    return;
                }
                else{
                    DeleteResponse deleteResponse = getDeleteResponse(indexId);
                    if (!deleteResponse.status().equals(RestStatus.OK)) {
                        log.info("从ES删除文档失败:{}",indexId);
                    }
                }
            }
        }
        catch (Exception e){
            log.error("操作ES发生异常",e.getMessage());
        }
    }



    @Override
    public void initWikiTemplate()  {
        try {
            if(checkTemplateExists(LogKbWikiAlias)){
                log.info("索引已经存在");
                return;
            }
            PutIndexTemplateRequest request = new PutIndexTemplateRequest(LogKbWikiAlias);
            request.alias(new Alias(LogKbWikiAliasName));
            request.patterns(CollUtil.newArrayList(LogKbWikiAlias + "*"));
            request.settings(getSetting());
            request.mapping(getMapping());
            request.create(false);
            AcknowledgedResponse response = restHighLevelClient.indices().putTemplate(request, RequestOptions.DEFAULT);
            if (response.isAcknowledged()) {
                log.info("创建模版成功!");
            } else {
                log.error("创建模版失败!");
            }
        }catch (Exception e){
            log.error("创建模版失败,请检查es连接");
        }
    }

    private Settings.Builder getSetting() {
        return Settings.builder()
                .put("index.refresh_interval", "60s")
                .put("index.translog.durability", "async")
                .put("index.translog.sync_interval", "600s")
                .put("index.number_of_shards", "2")
                .put("index.number_of_replicas", "0")
                .put("index.max_result_window", "10000");
    }

    private XContentBuilder getMapping() throws IOException {
        XContentBuilder jsonMapping = XContentFactory.jsonBuilder();
        jsonMapping.startObject().startObject("properties")
                .startObject("title").field("type", "text").field("analyzer", "ik_smart").endObject()
                .startObject("content").field("type", "text").field("analyzer", "ik_smart").endObject()
                .startObject("keywords").field("type", "text").field("analyzer", "ik_smart").endObject()
                .startObject("keywordsMax").field("type", "keyword").endObject()
                .startObject("topics").field("type", "keyword").endObject()
                .startObject("createBy").field("type", "keyword").endObject()
                .startObject("createTime").field("type", "date").field("format", "yyyy-MM-dd HH:mm:ss").endObject()
                .startObject("source").field("type", "text").endObject()
                .endObject().endObject();
        return jsonMapping;
    }

    private UpdateResponse getUpdateResponse(String indexId, WikiDTO wikiDTO) throws IOException {
        UpdateRequest updateRequest = new UpdateRequest(getBuildIndex(), indexId);
        updateRequest.timeout(TimeValue.timeValueHours(SaveTimeOutHours));
        updateRequest.doc(new JSONObject(wikiDTO,
                        new JSONConfig().setDateFormat(DatePattern.NORM_DATE_PATTERN)).toString()
                , XContentType.JSON);
        updateRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
        UpdateResponse updateResponse = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
        return updateResponse;
    }

    private IndexResponse getAddIndexResponse(WikiDTO wikiDTO) throws IOException {
        //插入数据，index不存在则自动根据匹配到的template创建。index没必要每天创建一个，如果是为了灵活管理，最低建议每月一个 yyyyMM。
        IndexRequest indexRequest = new IndexRequest(getBuildIndex());
        indexRequest.timeout(TimeValue.timeValueHours(SaveTimeOutMinutes));
        indexRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
        indexRequest.source(new JSONObject(wikiDTO,
                        new JSONConfig().setDateFormat(DatePattern.NORM_DATETIME_PATTERN)).toString()
                , XContentType.JSON);
        IndexResponse response = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
        return response;
    }


    private  boolean checkTemplateExists(String templateName) throws IOException {
        IndexTemplatesExistRequest request = new IndexTemplatesExistRequest(templateName);
        return restHighLevelClient.indices().existsTemplate(request, RequestOptions.DEFAULT);
    }

    private DeleteResponse getDeleteResponse(String indexId) throws IOException {
        DeleteRequest deleteRequest = new DeleteRequest(getBuildIndex(), indexId);
        deleteRequest.timeout(TimeValue.timeValueHours(SaveTimeOutHours));
        deleteRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
        DeleteResponse deleteResponse = restHighLevelClient.delete(deleteRequest,RequestOptions.DEFAULT);
        return deleteResponse;
    }

    private SearchResponse getSearchResponse(String indexId) throws IOException {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        QueryBuilder queryBuilder = QueryBuilders.idsQuery().addIds(indexId);
        searchSourceBuilder.query(queryBuilder);
        //超时 10S
        searchSourceBuilder.timeout(new TimeValue(SearchTimeOutSeconds, TimeUnit.SECONDS));
        SearchRequest searchRequest = new SearchRequest();
        searchRequest.source(searchSourceBuilder);
        searchRequest.indices(LogKbWikiAliasName);

        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        return searchResponse;
    }

    private WikiDTO buildWikiDTO(CreateWikiVO createWikiVO) {
        String keywordString = "";
        String content= createWikiVO.getContent();
        if(content != null){
            keywordString += content;
        }
        String title= createWikiVO.getTitle();
        if(title != null) {
            keywordString = keywordString + "," + title;
        }

        List<String> paramKeywordList = getIkAnalyzeSearchTerms(keywordString);
        //keyword不做分词
        String keyword= createWikiVO.getKeywords();
        if(keyword != null){
            paramKeywordList.add(keyword);
        }
        createWikiVO.setKeywords(KmStringUtils.concatListToString(paramKeywordList));
        WikiDTO wikiDTO = convertToEsVo(createWikiVO);
        return wikiDTO;
    }


    private List<String> getIkAnalyzeSearchTerms(String searchContent){
        List<String> result = new ArrayList<>();
        try {
            if(!searchContent.isEmpty()) {
                AnalyzeRequest analyzeRequest = AnalyzeRequest.withIndexAnalyzer(getBuildIndex(),
                        "ik_smart",searchContent);
                AnalyzeResponse analyzeResponse = restHighLevelClient.indices().analyze(analyzeRequest, RequestOptions.DEFAULT);
                analyzeResponse.getTokens().forEach((e) -> result.add(e.getTerm()));
            }
        } catch (Exception e) {
            log.error("无数据，创建索引失败，或者es连接异常");
        }
        return  result;
    }

    public WikiDTO convertToEsVo(CreateWikiVO wiki){
        WikiDTO wikiDTO = new WikiDTO();
        wikiDTO.setContent(wiki.getContent());
        wikiDTO.setCreateBy(wiki.getCreateBy());
        wikiDTO.setKeyword(wiki.getKeywords());
        if(wiki.getKeywords() !=null && wiki.getKeywords().length()>0)
            wikiDTO.setKeywords(wiki.getKeywords().split(","));
        wikiDTO.setTitle(wiki.getTitle());
        if(wiki.getTopics() != null && wiki.getTopics().length()>0)
            wikiDTO.setTopics(wiki.getTopics().split(","));
        wikiDTO.setSource(wiki.getSource());
        return wikiDTO;
    }
}
